package com.moonshineproject.atomic.multiplayer
{
	import com.moonshineproject.atomic.EditorTextArea;
	import com.moonshineproject.atomic.parser.Diff;
	import com.moonshineproject.atomic.parser.FauxParser;
	
	import mx.core.IUITextField;
	
	public class SyncHandler
	{
		
		public var editor:EditorTextArea;
		public var parser:FauxParser;
		
		public var history:Array = [];
		
		private var localChanges:Array = [];
		private var remoteChanges:Array = [];
		
		private var localChangesWhileLocked:Array = [];
		private var remoteChangesWhileLocked:Array = [];
		
		private var locked:Boolean = false;
				
		private function get hasRemoteChanges():Boolean {
			if (remoteChanges && remoteChanges.length)
				return true;
			return false;
		}
		
		// Lock editor, don't allow more input until unlock
		private function lock():void {
			// Maybe just store away text & merge in the next sync loop?
			locked = true;
		}
		
		// Unlock editor, add changes added while locked
		private function unlock():void {
			locked = false;
			localChanges = localChanges.concat(localChangesWhileLocked);
			remoteChanges = remoteChangesWhileLocked.concat(remoteChangesWhileLocked);
		}
		
		private function revert(startPsuedoTime:int, changes:Array):void {
			// Loop back in history and undo until startPsuedoTime is reached.
			changes.sortOn('psuedoTime');
			for (var i:int = changes.length-1; i > 0; i--) {
				var change:Diff = changes[i];
				if (change.psuedoTime >= startPsuedoTime && change.applied) {
					// TODO: Add revert code
					if (change.diff > 0) {
						editor.getTextField().replaceText(change.start, change.end+change.diff, "");
						parser.handleChange(change.start, change.end+change.diff);
						change.applied = false;
					} else {
						editor.getTextField().replaceText(change.start, change.start, change.change);
						parser.handleChange(change.start, change.end+change.diff);
						change.applied = false;
					}
				}
			}
		}
		
		public function addChange(diff:Diff, local:Boolean=true):void {
			diff.applied = diff.local = local;
			if (locked) {
				if (local)
					localChangesWhileLocked.push(diff)
				else
					remoteChangesWhileLocked.push(diff)
			} else {
				if (local)
					localChanges.push(diff)
				else
					remoteChanges.push(diff)
			}
		} 
		
		public function doSync():void {
			if (hasRemoteChanges) {
				
				lock();
				
				var changes:Array = localChanges.concat(remoteChanges);
				changes = changes.sortOn('psuedoTime'); // Changes are of type Diff
				
				// Revert changes so we have a nice lil' base to work on
				var startAt:int = changes[0].psuedoTime;
				changes = history.concat(changes);
				changes = changes.sortOn('psuedoTime');
				
				revert(startAt, changes);
				
				for (var i:int = 0; i < changes.length; i++) {
					var change:Diff = changes[i] as Diff;
					if (change.psuedoTime >= startAt) {
						applyChange(change);
					
						adjustFutureChanges(i, change, changes);
					}
				}
				
				history = changes;
				
				localChanges = [];
				remoteChanges = [];
				
				unlock();
			} else {
				history = history.concat(localChanges);
				localChanges = [];
			}
			
			
			if (history.length > 20) {
				history = history.slice(history.length-20, history.length);
			}
		}
		
		private function adjustFutureChanges(start:int, change:Diff, changes:Array):void {
			for (var i:int = start+1; i < changes.length; i++) {
				var fc:Diff = changes[i];
				if (change.local && !fc.local) {
					if (fc.start >= change.start) {
						fc.start += change.diff;
						fc.end += change.diff;
					} else {
						if (fc.start >= change.start) {
							fc.start -= change.diff;
							fc.end -= change.diff;
						}
					}
				}
			}
		}
		
		public function applyChange(change:Diff):void {
			var f:IUITextField = editor.getTextField();
			if (change.change == "\r")
				change.change = "\n";
			
			if (change.diff < 0)
				editor.getTextField().replaceText(change.start, change.end, "");
			else
				editor.getTextField().replaceText(change.start, change.end, change.change);
			change.applied = true;
			
			parser.handleChange(change.start, change.end);
		}

	}
}